#include "common.h"
#include "counter.h"
#include "Vrename_test.h"

#define TRACE 0
#define MAX_WAVE 1000000
#define MAX_CYCLE 1000000
Vrename_test top;
VerilatedFstC *tfp;
static uint64_t wave_cnt = 0;

#define RENAME_WIDTH 4
#define COMMIT_WIDTH 2
#define PRF_SIZE 92
bool percent(int change)
{
    uint32_t r = rand();
    r %= 100;
    return r < change;
}
void dump()
{
#if TRACE
    if (wave_cnt < MAX_WAVE)
    {
        tfp->dump(Verilated::time());
        wave_cnt++;
    }
#endif
}
void run()
{
    top.eval();
    dump();
    Verilated::timeInc(1);

    top.clk = 0;
    top.eval();
    dump();
    Verilated::timeInc(2);

    top.clk = 1;
    top.eval();
    dump();
    Verilated::timeInc(1);
}
void reset()
{
    top.i_valid = 0;
    top.o_ready = 1;

    for (size_t i = 0; i < COMMIT_WIDTH; i++)
    {
        top.i_commit_valid[i] = 0;
    }
    top.rst = 1;
    run();
    run();
    run();
    top.rst = 0;
}

Counter cycle("cycle");
#include <queue>

typedef struct
{
    uint64_t cycle;
    int pdst;
    int free_pdst;
    bool has_gc;
    int freelist_rptr;
    bool has_rd;
    int rd;
} ROB_Entry;
Counter op_cnt("op_cnt");
Counter commit_cnt("commit_cnt");
Counter flush_cnt("flush_cnt");
void make_new_op()
{
    // LOG("Make new op at %lu", Verilated::time());
    for (size_t i = 0; i < RENAME_WIDTH; i++)
    {
        top.i_fop_valid[i] = 0;
    }
    uint32_t new_op_cnt = rand();
    new_op_cnt %= (RENAME_WIDTH + 1);
    for (size_t i = 0; i < new_op_cnt; i++)
    {
        // rs0
        uint32_t r = rand();
        r %= 32;
        top.i_fop_dop_rs1[i] = r;

        // rs1
        r = rand();
        r %= 32;
        top.i_fop_dop_rs2[i] = r;

        // rd
        r = rand();
        r %= 100;
        bool has_rd = percent(80);
        top.i_fop_dop_has_rd[i] = 0;
        top.i_fop_dop_rd[i] = 0;
        if (has_rd)
        {
            r = rand();
            r %= 32;
            top.i_fop_dop_rd[i] = r;
            top.i_fop_dop_has_rd[i] = r != 0;
        }
        top.i_fop_valid[i] = 1;
    }
}

ROB_Entry read_rob(int index)
{
    ROB_Entry rob_entry;
    rob_entry.cycle = cycle;
    rob_entry.free_pdst = top.o_rop_free_pdst[index];
    rob_entry.freelist_rptr = top.o_rop_freelist_rptr[index];
    rob_entry.pdst = top.o_rop_pdst[index];
    rob_entry.has_gc = top.o_rop_has_gc[index];
    rob_entry.has_rd = top.o_rop_has_rd[index];
    rob_entry.rd = top.o_rop_ard[index];
    return rob_entry;
}

void rename_test()
{
    LOG("rename_test");
    int arat[32] = {0};
    int crat[PRF_SIZE] = {0};
    for (size_t i = 0; i < 32; i++)
    {
        crat[i] = 1;
        arat[i] = i;
    }

    std::queue<ROB_Entry> rob;
    reset();
    size_t test_cnt = 0;
    bool could_commit = true;
    int stall_cnt = 0;
    do
    {
        bool redirected = false;
        if (top.i_commit_ready)
        {
            for (size_t i = 0; i < COMMIT_WIDTH; i++)
            {
                bool restore = top.i_commit_valid[i] & top.i_commit_data_restore_gc[i];
                if (restore)
                {
                    int free_cnt = 0;
                    while (!rob.empty())
                    {
                        flush_cnt++;
                        if (rob.front().has_rd)
                            free_cnt++;
                        rob.pop();
                    }
                    if (top.o_valid & top.o_ready)
                    {
                        for (size_t i = 0; i < RENAME_WIDTH; i++)
                        {
                            if (top.o_rop_valid[i])
                            {
                                auto rob_entry = read_rob(i);
                                if (rob_entry.has_rd)
                                {
                                    free_cnt++;
                                }
                            }
                        }
                    }
                    // LOG("Free PRF cnt : %d", free_cnt);
                    for (size_t i = 0; i < PRF_SIZE; i++)
                    {
                        crat[i] = 0;
                    }
                    for (size_t i = 0; i < 32; i++)
                    {
                        crat[arat[i]] = 1;
                    }
                    redirected = true;
                    break;
                }
            }
        }

        if (!redirected && (top.o_valid & top.o_ready))
        {
            for (size_t i = 0; i < RENAME_WIDTH; i++)
            {
                if (top.o_rop_valid[i])
                {
                    auto rob_entry = read_rob(i);
                    if (rob_entry.has_rd)
                    {
                        Assert(crat[rob_entry.pdst] == 0, "[%lu] allocate reg %d is used", i, rob_entry.pdst);
                        Assert(crat[rob_entry.free_pdst] == 1, "[%lu] old reg %d is not used", i, rob_entry.free_pdst);
                        crat[rob_entry.pdst] = 1;
                        crat[rob_entry.free_pdst] = 0;
                        // LOG("Rename ard %2d prd from %3d to %3d at %lu", rob_entry.rd, rob_entry.free_pdst, rob_entry.pdst, Verilated::time());
                    }
                    rob.push(rob_entry);
                    op_cnt++;
                }
            }
        }
        run();
        cycle++;
        if (top.i_ready)
        {
            top.i_valid = 0;
            stall_cnt = 0;
        }
        else
        {
            stall_cnt++;
            Assert(stall_cnt < 1000, "stall too long");
        }

        if (top.i_commit_ready)
        {
            could_commit = true;
            for (size_t i = 0; i < COMMIT_WIDTH; i++)
            {
                top.i_commit_valid[i] = 0;
            }
        }
        if (top.i_valid == 0)
        {
            if (percent(90))
            {
                make_new_op();
                top.i_valid = 1;
            }
        }
        top.o_ready = percent(80);

        if (could_commit)
        {
            could_commit = false;
            uint32_t commit_num = rand();
            commit_num %= (COMMIT_WIDTH + 1);
            for (size_t i = 0; i < commit_num; i++)
            {
                if (rob.empty())
                {
                    break;
                }
                auto rob_front = rob.front();
                if (rob_front.cycle + 5 > cycle)
                {
                    break;
                }
                commit_cnt++;
                rob.pop();
                if (rob_front.has_rd)
                {
                    arat[rob_front.rd] = rob_front.pdst;
                    // LOG("Free reg %d, use reg %d", rob_front.free_pdst, rob_front.pdst);
                }
                top.i_commit_valid[i] = true;

                top.i_commit_data_has_gc[i] = rob_front.has_gc;
                top.i_commit_data_has_rd[i] = rob_front.has_rd;
                top.i_commit_data_free_entry[i] = rob_front.free_pdst;
                top.i_commit_data_restore_freelist_rptr[i] = rob_front.freelist_rptr;
                top.i_commit_data_restore_gc[i] = 0;
                if (rob_front.has_gc)
                {
                    bool restore = percent(2);
                    top.i_commit_data_restore_gc[i] = restore;
                    if (restore)
                    {
                        LOG("Restore gc at %lu", Verilated::time());
                        break;
                    }
                }
                // LOG("Commit op at %lu", Verilated::time());
            }
        }
    } while ((!Verilated::gotFinish()) && cycle < MAX_CYCLE);
}

int main()
{
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top.trace(tfp, 0);
    tfp->open("Vrename_test.fst");
#endif

    srand(0);
    Verilated::randSeed(0);
    Verilated::randReset(0);
    reset();

    rename_test();
    // Final model cleanup
    top.final();

#if TRACE
    tfp->close();
    delete tfp;
#endif
    Counter::print_counters();
    LOG("All test done");
    return 0;
}